グレー障害について理解を深める Detecting and mitigating gray failures #ARC310 #AWSreInvent
コンバンハ、千葉(幸)です。
皆さんは Gray failures (グレー障害)という言葉を知っているでしょうか。
特に ELB に関連した AWS アップデートの中で Gray failures というワードを聞くことが多くなってきた気がしたので、理解を深めるために re:Invent 2023 の以下セッションに現地参加してきました。(2ヶ月半前に。)
- [ARC310] Detecting and mitigating gray failures
このブログではセッションレポートをまとめます。
セッションの要点まとめ
個人的に学びがあった要点のまとめです。
- システム全体では障害として検出されないが、利用者にとっては HEALHTY でない状態をグレー障害と呼ぶ
- ELB のヘルスチェック(シャロー or ディープ)は構成に応じて選択すべきものが異なる
- データプレーンはコントロールプレーンと比較して障害発生率が低いため、なるべくコントロールプレーンに依存しない障害回避の手段を取るべき
特に、シャローヘルスチェックとディープヘルスチェック、それぞれが適する/適さないケースを学べたのが一番の収穫でした。
セッション概要
セッションカタログから引用した概要はこちら。
Gray failures can escape quick and definitive detection while impacting workloads running in the cloud. Building resilient applications that can survive single instance and single Availability Zone gray failures requires you to implement specific observability and mitigation strategies. In this session, learn what gray failures are and how they can manifest in the cloud. Explore detection capabilities like Amazon CloudWatch Contributor Insights, composite alarms, and outlier detection to help you discover gray failures. Learn how tools like ELB health checks, Amazon EC2 Auto Scaling, the heartbeat table pattern, and Amazon Route 53 Application Recovery Controller zonal shift can be used to mitigate the impact of these failures.
(機械翻訳)
グレー障害は、クラウドで実行されているワークロードに影響を与えながら、迅速かつ明確な検出から逃れることができます。単一インスタンスや単一アベイラビリティ・ゾーンのグレー障害に耐える回復力のあるアプリケーションを構築するには、特定の観測可能性と緩和戦略を実装する必要があります。このセッションでは、グレー障害とは何か、クラウドでどのように顕在化するかを学びます。Amazon CloudWatch Contributor Insights、複合アラーム、異常値検出などの検出機能を使用して、グレー障害の発見を支援します。ELBのヘルスチェック、Amazon EC2 Auto Scaling、ハートビートテーブルパターン、Amazon Route 53 Application Recovery Controllerのゾーンシフトなどのツールを使用して、これらの障害の影響を軽減する方法を学びます。
このセッションは YouTube で公開されています。
PDF の資料はこちらです。
セッションレポート
セッションの内容をベースに、わたしの言葉に置き直してレポートを書いていきます。(聴講者の立場とセッションの内容を説明する立場がたまに混在します。)
1. グレー障害の影響は大きい
セッションは冒頭にサービスの可用性のグラフが表示されるところから始まります。
特定のイベント発生中にサービス可用性が 3 % 程度下落しています。このイベントの発生中、果たして何台のホストに障害が起こっていたのでしょうか?
正解は、1台です。シングルホストのグレー障害によって、サービス全体の可用性が 3 % 程度も下がり得る、という事象が共有されました。
2. グレー障害とは何か
グレー障害は Differential observability(視点別のオブザーバビリティ)という概念によって定義されます。言い換えると、視点によって HEALTHY であるか UNHEALTHY であるかが異なる、ということです。
例として、あるシステムの依存関係の抽象化モデルが示されました。ここでは、以下の内容となっています。
- コアとなるビジネスロジックと、そこに依存するアプリが 3 つある
- 障害を検出するオブザーバーと、問題解決を行うリアクターがある
- オブザーバーは各アプリの平均レイテンシを観測する
- システムとしての閾値はレイテンシ 60 ms が設定されている
アプリ1,2,3 はそれぞれ通常 60ms 以下のレイテンシを維持しています。ここで、アプリ 1 で、通常 50ms 程度のレイテンシが 70 ms まで増加したとします。
この時、システム全体としての平均レイテンシは依然として 60 ms を下回っており(59.66ms)、オブザーバーによる検出、リアクターの作動は行われません。
一方で、アプリ1 のユーザーの視点では通常よりレイテンシが 40 % も増加しています。レイテンシの増加は他の依存関係の呼び出しに連鎖的に影響を与える可能性があります。アプリ1のワークロードにおいては障害が発生していると言えるでしょう。
システムとしては障害が検出されていないものの、特定のワークロードでは障害が発生している。まさにグレー障害が発生している状態です。
カスタマーの視点、システムの視点で 4象限のマトリクスを取ったものが以下の図です。ここでの右上がグレー障害です。
グレー障害はやがて検出された障害(Detected Failuer)となり復旧されることもありますが、それには時間を要する場合もあります。(逆に検出された障害がグレー障害に遷移することもある。)
迅速にグレー障害に対応するためには、システムの視点とは異なる視点で障害を検出する必要があります。グレー障害の検出と緩和、というセッションタイトルはここに繋がってきます。
AWS における障害分離境界としては、以下が挙げられます。
- リージョン
- アベイラビリティゾーン(AZ)
- インスタンス/コンテナ
- ソフトウェアモジュールもしくはソフトウェアコンポーネント
このセッションにおいては、シングルホスト(シングルインスタンス)のグレー障害、シングル AZ でのグレー障害が取り上げられます。
3. シングルインスタンスのグレー障害
ディープヘルスチェックとシャローヘルスチェックのトレードオフ
シングルホストで起こるグレー障害の例として以下が取り上げられます。
- ELB 配下の EC2 インスタンスの例
- 顧客からのリクエストに 5xxエラーを返す
- ELB からのヘルスチェックには正常に応答する
- ELB からは引き続きトラフィックがルーティングされるため、顧客にステータスエラーを返す
- ロックテーブルとして DynamoDB を利用する EC2 インスタンスの例
- 分散ロックテーブルとして DynamoDB を利用する EC2 インスタンス群がある
- 単一のインスタンスが特定のテーブルのデータをロックした状態で処理に失敗
- テーブルに対してハートビートを続けられる程度には正常に稼働を続けている
- データがロックされているため他の EC2 インスタンスが当該処理を続行できない
上記のようなグレー障害への対応策として、ディープヘルスチェックを導入するのはどうでしょうか。
おさらいとして、ヘルスチェックの種類には「シャロー(浅い)ヘルスチェック」と「ディープヘルスチェック」があります。
シャローヘルスチェックはサーバーおよびアプリケーションまでをチェックします。それに対してディープヘルスチェックは依存関係まで含めてチェックを行います。例えば DB にクエリを実行できるか、といった具合です。
単純に考えれば上記のケースにおいてはディープヘルスチェックは有効な対応策に見えます。もう少し具体的に、Auto Scaling グループ配下のインスタンス群について考えます。
ELB 配下に Auto Scaling グループ構成のインスタンス群があり、それらが依存関係を持つ(例えばデータベースに接続し値を読み書きする)、という構成です。
Auto Scaling グループのヘルスチェックには以下の 2種類が存在します。
- EC2 インスタンスステータスチェック(デフォルト)
- ELB ヘルスチェック
1の EC2 インスタンスステータスチェックはデフォルトで有効となっており、無効化できません。1 のみにするか 1 と 2 を組み合わせるかをカスタマー側が選択できます。
ここで、以下の組み合わせについて、いくつかの主要なパターンを考えてみます。(セッションで取り上げられていないパターンも含んでいます。)
- Auto Scaling グループのヘルスチェックが EC2 のみか ELB も含むか
- ELB のヘルスチェックがシャローかディープか
- 障害発生箇所がどこか
構成として望ましい状態であるかどうかを、「評価」列に記載します。
# | ASGヘルスチェック | ELBヘルスチェック | 障害発生箇所 | どうなる | 評価 |
---|---|---|---|---|---|
1 | EC2のみ | シャロー | インスタンス内部 | ELBのルーティング対象外に | NG(不要なインスタンスが残る) |
2 | EC2+ELB | シャロー | インスタンス内部 | 当該インスタンスの置き換え | OK |
3 | EC2のみ | シャロー | 依存関係との接続 | グレー障害 | NG(クライアントにエラーが返る) |
4 | EC2+ELB | シャロー | 依存関係との接続 | グレー障害 | NG(クライアントにエラーが返る) |
5 | EC2のみ | ディープ | インスタンス内部 | ELBのルーティング対象外に | NG(不要なインスタンスが残る) |
6 | EC2+ELB | ディープ | インスタンス内部 | 当該インスタンスの置き換え | OK |
7 | EC2のみ | ディープ | 依存関係との接続 | ELBのルーティング対象外に | OK |
8 | EC2+ELB | ディープ | 依存関係との接続 | 当該インスタンスの置き換え | OK? |
9 | EC2+ELB | ディープ | 依存関係側(DBなど) | すべてのインスタンスの置き換え | NG(正常なインスタンスが残らない) |
ここでの障害発生箇所の「インスタンス内部」とは、アプリケーションの動作に必要なプロセスの障害などを指します。インスタンスそのものは正常稼働している状態です。
#1 の場合、ELBのヘルスチェックに失敗し、インスタンスはルーティングの対象外になります。EC2 インスタンスの置き換えは発生しないため、不要なインスタンスが残り続けることになります。
#2 の場合、ELB のヘルスチェックの失敗を契機に Auto Scaling Group によるインスタンスの置き換えが発生します。このケースにおいては、望ましい状態です。
#3,#4のように障害が発生箇所が「依存関係との接続」の場合、シャローヘルスチェックによって検出されません。ELB は引き続きインスタンスにトラフィックをルーティングするため、グレー障害が発生します。
#3,4 のケースへの対策として、ディープヘルスチェックを導入することを考えます。障害発生箇所が「インスタンス内部」である場合、シャローヘルスチェックとの差はありません。(#5,6は#1,2と同様)
#7 の場合、シャローヘルスチェックでは検出できなかった依存関係とのグレー障害に対応しており、かつインスタンスは保持されるため、望ましい状態であるとも言えます。
#8 になると、評価が難しくなります。単一のインスタンスと依存関係の接続でのみ頻繁にエラーが発生しているケースであれば、インスタンスごと置き換える挙動が望ましい場合も考えられます。
#9 になると、明確に望ましくない状態です。例えば DB そのものに一時的に障害発生し、いずれのインスタンスからも接続ができなくなったケースを考えます。ディープヘルスチェックにより DB との接続が確認できないため、全てのインスタンスがヘルスチェックに失敗し、Auto Scaling による置き換えが行われます。
置き換えのプロセスにも一定の時間を要するため、ユーザーには長めのダウンタイムが発生することになります。
ここまで見て来たように、シャローヘルスチェックとディープヘルスチェックはトレードオフの関係にあります。明確にどちらが優れているということはなく、使用のポイントを見極める必要があります。
ディープヘルスチェックの場合は Auto Scaling Group との統合は避けるべきでシャローヘルスチェックの場合は Auto Scaling Group との統合を行うべき、それぞれで発生する考慮事項を別のメカニズムでカバーする必要あり、ということを覚えておきましょう。
ディープヘルスチェック時用のハートビートテーブル
ディープヘルスチェックを使用する場合はAuto Scaling Group との統合(Auto Scaling GroupのヘルスチェックにELBのヘルスチェックを使用する)を避けるべき、となると、不要なインスタンスが残り続ける課題が発生します。先ほどの表における #1,#5のケースです。
それに対応するメカニズムの案の一つとして、DynamoDB と Lambda を利用したハートビートテーブルがあります。
この仕組みは以下のようなものです。
- 各インスタンスは ELB からヘルスチェックを受ける度に、DynamoDB にレコードを書きこむ
- Lambda 関数は定期的に DynamoDB のテーブルをチェックし、一定期間更新がないインスタンスがあった場合
SetInstanceHealth
によりAuto Scaling Groupのヘルスチェックに失敗させる
ELB のヘルスチェックで全部がわかるわけではない
ここまで ELB のヘルスチェックについて見てきましたが、ELB のヘルスチェックで全てのエラーがわかるわけではない、という課題があります。
一つは、依存関係が複数あった場合です。
依存関係が複数あった場合でも、ヘルスチェックでそのすべてをチェックすることはできません。単一の依存関係に対してのみヘルスチェックが行われた場合、そのほかの依存関係に関するエラーは見落とされることになります。
二つ目は、検知確率の低さです。
例えば EC2 インスタンス 2台構成で、片方のインスタンスが依存関係との接続に 4 % のエラー率を抱えているとします。ここで ELB のヘルスチェックが「3回連続でエラーが発生した場合に失敗と評価する」という設定だった場合、その発生確率は 0.0064 % となり、ほとんど検知されません。
4. シングルインスタンスのグレー障害の検知
シングルインスタンスのグレー障害を検知するためにはどうしたらいいでしょうか。
ここでは、計測器(Instrumentation)を用いてシステムに可観測性を持たせることが必要だとされています。そのためには以下が重要です。
- ビジネスロジックに基づいた明示的なコードの記述
- out-of-the-box(箱から出したばかりで手を加えていない状態)ではなく必要に応じたカスタマイズ
- クライアントサイドとサーバーサイド双方の視点
- リージョン、AZ、ホストといった障害分離境界に合わせてディメンションを取得する
- ログとメトリクスを組み合わせるためにembedded metric format (EMF) を使用する
EMF はログとメトリクスを一元管理でき、強力なツールであると説明されています。そして、EMF で各インスタンスから収集したログを、Contributor Insights を用いて分析できます。
例えばインスタンスごとのエラーコードをまとめてグラフ描画できます。ここで外れ値となるインスタンスがあれば、グレー障害の発生を検知できます。
外れ値を検出するには、CloudWatch アラームをセットするか、Lambda 関数を作成することで実現できます。後続のアクションを含めての自動化もできます。
外れ値の検出および自動対応というアプローチにおいては、考慮すべきことがふたつあります。
- 根本的なエラーを隠蔽することがある
- 頻繁に外れ値を記録するインスタンスが発生する場合、コードにバグが含まれている、といった根底の原因がある可能性もある
- 自動アクションによってインスタンスが置き換えられる場合、その根底の原因にたどり着けなくなる可能性がある
- 誤検出が発生することがある
- 外れ値検出のアルゴリズムは完璧ではないため、グレー障害でない何かが検出される場合がある
5. シングルインスタンスのグレー障害の緩和
シングルインスタンスで発生したグレー障害を緩和する方法、それはシンプルです。
インスタンスを置き換えることです。
メモリの故障、CPUの故障、ネットワークの問題、メモリリーク、レースコンディションなど、グレー障害の原因となり得る事象は多々ありますが、それが新しいインスタンスでも同じように発生する可能性は低いです。
ただし、置き換えの速度は考慮する必要があります。あまりにも頻繁にインスタンスの終了を試みた場合、AWS 側で人の目の確認が入るようです。
6. シングル AZ のグレー障害の検出
ここまではシングルインスタンスにおけるグレー障害を取り扱ってきました。同じロジックをシングル AZ におけるグレー障害にも適用できます。
シングル AZ のグレー障害が発生するシナリオ、およびその検出方法は以下のとおりです。
# | シナリオ | 検出方法 |
---|---|---|
1 | 複数のAZで影響があり、特に1つのAZでの影響が大きい | 外れ値の検出 |
2 | 単一のAZでのみ影響があり、他のAZでは影響がない | 外れ値の検出、CloudWatch複合アラーム |
3 | グレー障害が発生する単一のAZのリソースを共有していることで、他のAZやリソースに影響が出る | CloudWatch複合アラーム |
複合アラームを用いたシングル AZ でのみ発生するグレー障害の検出
ここでは、新たに出てきた CloudWatch 複合アラームについて見ていきます。これは、複数の CloudWatch アラームを OR や AND などで繋ぎ、複数のステータスを組み合わせた新たなアラームにするものです。
まずはシナリオ2のケースに対応する複合アラームを考えます。
まず、以下のようにAZ 1 におけるHome
とListProducts
というふたつのサービスの、それぞれの可用性を確認するアラームを作成できます。ここでは、「3回中3回」と「5回中3回」ということなる頻度での評価を組み合わせています。
すべてのアラームを OR で結合しているため、いずれかのアラームが閾値を超えれば検出されます。個々のアラームを別個に管理するより簡素化されます。
可用性の他にも、レイテンシーなど、必要な観点の複合アラームを作成します。同様のものを他の AZ でも作成します。
これらの複合アラームをさらに複合することで、AZ 単位の障害の分離に基づいた検出を実現できます。AZ 1に関するアラームを OR で、それ以外の AZ に関するアラームを AND NOT (と OR )で繋ぐことで、AZ 1 に関する影響があるもののみを検出できます。
ただしこの状態ではシングルインスタンスが起因なのか AZ 単位なのかの判別ができません。よって、contributor insights rule を使用し、複数のインスタンスからメトリクスが発行されていることを評価するアラームを作成します。それと AND で繋ぐことで、シングル AZ で発生したグレー障害を検出できるアラームが完成します。
複合アラームを用いたマルチ AZ に影響があるシングル AZ グレー障害の検出
続いて、シナリオ3の「共有リソースがマルチ AZ に影響を及ぼすケース」を検出するための複合アラームの設定例です。このシナリオは、単一 AZ に存在する DB インスタンスにグレー障害が発生することで、それを使用する各 AZ のインスタンスに影響が出る、といったものが想定されています。
ここでは、3つある AZ のうち 2 つの AZ のメトリクスを複合する方式が紹介されます。例えば以下のイメージです。
「各 AZ における可用性に関するアラーム(画面上部)」と「各 AZ におけるレイテンシに関するアラーム(画面下部)」は、それぞれ「AZ 1 と AZ 2」「AZ 1 と AZ 3」「AZ 2 と AZ 3」の組み合わせで複合アラームが作成されます。
これにより、マルチ AZ に影響を与えるグレー障害が検出できます。最後に「可用性」と「レイテンシ」のアラームを統合することで、共有リソースがマルチ AZ に影響を及ぼす事象を検出するアラームが完成します。
7. シングル AZ のグレー障害の緩和
シングル AZ のグレー障害が発生した場合、取り得る対策は 3 つあります。
- 自然に解消するのを待つ(多くのカスタマーはこれを選ぶ)
- シングル AZ にのみ影響がある場合、別の AZ に避難する
- マルチリージョン DR プランがあるのであれば、別のリージョンにフェールオーバーする(考慮事項がたくさんある)
ここでは、2番の「AZ からの避難」の詳細が取り上げられます。AZ からの避難には 2つのゴールがあります。
- プロセスの停止
- 例)
- 当該 AZ に送られてくる HTTP や gRPC などの各種リクエスト
- 当該 AZ 内で行われているバッチ処理などのプロセス
- 例)
- 新しい容量のデプロイの停止
- 例)
- Auto Scaling によってデプロイされるリソース
- スケジューリングによりデプロイされるコンテナ
- 例)
そして、AZ からの避難には 2 つの考慮事項があります。
- 効果的な避難のためにはAZ の独立性が維持されている必要がある
- 例)
- ELB のクロスゾーン負荷分散が無効で、AZに閉じている
- VPC エンドポイントのリージョナル DNS 名でなくゾーナル DNS 名を使用する
- RDS のリードレプリカを各 AZ に作成し、クライアントに同一 AZ のリードレプリカを使用するようにコントロールする
- 例)
- なるべくコントロールプレーンよりデータプレーンに依存するようにする
- サービスのコントロールプレーンはデータプレーンより複雑であり、障害発生率が高い
- Auto Scaling Group などコントロールプレーンに依存せざるを得ないアーキテクチャもある
- あらかじめインスタンスをデプロイしておき DNS 操作によって切り替えを行う、という方式はデータプレーンに依存した方式であり、新規インスタンスを作成する(コントロールプレーン依存)方式より望ましい
データプレーンに関する話が後続のセクションで取り上げられます。
8. データプレーンのコントロールによる避難
Zonal Shift による AZ の切り離し
データプレーンのコントロールによるシングル AZ のグレー障害からの避難の方式として、Amazon Route 53 Application Recovery Controller zonal shift が紹介されます。
これは、クロスゾーン負荷分散が無効な ALB/NLB において、指定した AZ へのトラフィックを停止させる機能です。裏側では、 DNS 操作により特定の AZ のノードの IP アドレスを返却しなくしているだけである、と説明されています。
AZ ヘルスの一元化されたビュー
Zonal Shift は個別の ALB/NLB を指定して行うものでした。これを一元化して行う手法もあります。
- API Gateway と DynamoDB を統合する
- API Gateway へのリクエストにより DynamoDB テーブルの値が取得される
- DynamoDB テーブルには AZ の一覧と Healthy かどうかが記録されている
- API Gateway で、ALB/NLB の各ノードのゾーナル DNS 名を指定する形でヘルスチェック用のパスを用意する
- Route 53 ヘルスチェックで API Gateway を宛先にヘルスチェックを作成する
ここで、例えば AZ 3 でグレー障害が検出された場合、DynamoDB テーブルの Healthy の値を人の手で書き換えます。そうすると AZ 3 に対する Route 53 ヘルスチェックは失敗するため、当該ノードの IP アドレスは返却されなくなります。
個々の ALB/NLB に対して Zonal Shift を実施するのではなく、DynamoDB のレコードを書き換えるだけという点で一元化されています。Route 53 ヘルスチェック、DynamoDB への書き込みはデータプレーンに依存した機能です。
9. コントロールプレーンのコントロールによる避難
ここまで見てきた方法はデータプレーン依存の方式でしたが、コントロールプレーン依存にならざるを得ないアーキテクチャもあります。例えば、ELB が存在せず SQS キューと Auto Scaling Group 配下の EC2 インスタンスの組み合わせからなるアーキテクチャなどです。
そういったアーキテクチャで AZ からの避難を行うには、ローカルからのスクリプト実行が望ましいです。他のシステムやサードパーティのシステムからの実行を行う場合、それらとの依存関係が発生するため、なるべく依存するものを減らすためにローカルからの実行を行います。
AZ からの避難を行う処理
スクリプトでは避難したい AZ ID を指定して実行することで、以下の処理を行います
- リソースの一覧化
- どのサブネットを取り除くべきかの決定
- リソース一覧や取り除くサブネット、AZを記録(DynamoDB やその他 DB、CSVなど)
- ネットワーク設定を更新(リソースをサブネットから取り除く)
上記の処理が行われることによって、リソースは残ったサブネットで Auto Scaling などの自動復旧を行い、AZ 避難が完了します。
元の構成に戻す処理
AZ が回復したのち、元の構成に復旧させるにも同様ローカルからのスクリプト実行を行います。
- 避難処理の際に記録したリソース、AZ、サブネットの一覧を取得
- 対象のリソースの状態を確認
- 復旧させるサブネットの確認
- ネットワーク設定を更新
- 復旧完了後、退避処理の際に記録した内容を削除
10. まとめ
最後にここまでのまとめと共に締め括られます。
- グレー障害は「視点別のオブザーバビリティ」によって定義される
- グレー障害を「検出された障害」にしたい
- グレー障害を検出し緩和するためには、基盤となるサービスにだけ頼るのでなく自分で観測の仕組みを実装する必要がある
- メトリクス発行の際には、ホスト/AZといった障害分離境界に応じたディメンションを付与すること
- ELB や EC2 の標準のヘルスチェック以上の自前の計測器が必要になる
- 検出のために外れ値検出と CloudWatch 複合アラームを活用すること
セッションの終わりには、学習に役立つリソースの紹介が行われました。
学べること | 種別 | リンク |
---|---|---|
ELBのヘルスチェックの使い分け | ブログ | Choosing the right health check with Elastic Load Balancing and EC2 Auto Scaling |
AZ避難 | ワークショップ | Advanced Multi-AZ Resilience Patterns |
依存関係を持つシステムの障害ハンドリング | ワークショップ | Well-Architected Reliability |
Contributor Insightsによるグレー障害の検出 | ブログ | Detecting gray failures with outlier detection in Amazon CloudWatch Contributor Insights |
マルチAZでのレジリエンスのあるアーキテクチャ | ホワイトペーパー | Advanced Multi-AZ Resilience Patterns |
ワークショップに取り組むには本腰を入れる必要がありますが、ブログとホワイトペーパーはサッと読めてセッションの大部分をカバーしているため、目を通しておくと参考になると思います。
終わりに
ARC 310 Detecting and mitigating gray failures のセッションレポートでした。
当日は現地で聴講しましたが、慣れない英語を聞き取りながら写真を撮りながらメモしながら、というスタイルでなかなか苦労しました。後から動画とスライドが公開されたので、あらためて内容をおさらいするのに役立ちました。
「シャローヘルスチェックでは不十分だ」→「ディープヘルスチェックを使おう」→「ディープヘルスチェックでは困るケースがある」→「シャローヘルスチェックを使おう?」→「どっちを選べばいいんだ!」という、動画で言う 13:15 あたりのくだりは現地で見て笑ったので、個人的にはお気に入りのシーンです。
グレー障害について詳しくなった気がします。このレポートが皆さんの参考になっていれば幸いです。
以上、 チバユキ (@batchicchi) がお送りしました。